/*
 * Copyright (C) Pietro Pilolli 2010 <pilolli.pietro@gmail.com>
 * 
 * Spotlighter is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Spotlighter is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "callbacks.h"
#include "utils.h"


/* Main cairo context. */
static cairo_t *cr = (cairo_t *) NULL;

/* Used to shape input mask. */
static cairo_t *shape_cr = (cairo_t *) NULL;
static GdkBitmap *shape = (GdkBitmap *) NULL;

/* Define the outbounded rectangle of the spotlight. */
static gdouble min_x = 0.0;
static gdouble max_x = 0.0;
static gdouble min_y = 0.0;
static gdouble max_y = 0.0;

/* The cursor to be displayed. */
static GdkCursor *cursor = (GdkCursor *) NULL;

/* See the discriminate_zone function... */
static gint zone = 5;

static gboolean first_press_event = TRUE;

static gint screen_width = 0;
static gint screen_height = 0;


/* Quit the program. */
static void
quit ()
{
  if (cr)
    {
      cairo_destroy (cr);
    } 
  if (shape)
    {  
      g_object_unref (G_OBJECT (shape));
    }
  if (shape_cr)
    {
      cairo_destroy (shape_cr);
    }
  if (cursor)
    {
      gdk_cursor_unref (cursor);
    }
  gtk_main_quit ();
  exit (1);
}


/* Reset the shape used for the input mask. */
static void
reset_shape ()
{
  if (shape_cr)
    {
      cairo_set_operator (shape_cr, CAIRO_OPERATOR_SOURCE);
      cairo_set_source_rgba (shape_cr, 1, 1, 1, 1);
      cairo_paint (shape_cr);
      cairo_stroke (shape_cr);
    }
}


/* Draw the spotlighter hole. */
static void
draw (cairo_t *cr, gdouble x, gdouble y, gdouble width, gdouble height, gchar *color)
{
  if (cr)
    {
      cairo_draw_ellipse (cr, min_x, min_y, max_x - min_x, max_y - min_y, color);
    }
}


/* Is the point (x,y) inside the rectangle? */
static gboolean
is_in_rectangle (gdouble x, gdouble y, gdouble min_x, gdouble min_y, gdouble max_x, gdouble max_y)
{
  if ((min_x < x) && (x < max_x) && (min_y < y) && (y < max_y))
    {
      return TRUE;
    }
  return FALSE;
}


/* Discriminate the zone. */
static void
discriminate_zone (gdouble x, gdouble y)
{
  gint tollerance = 30;
  GdkCursorType type = GDK_FLEUR;
  zone = 5;

  /*
     This is the topologic zone explained:

	  1|1
       ---------
       |4|1|1|6|
       ---------
      0|0|5|5|2|2
      -----------
      0|0|5|5|2|2
       ---------
       |8|3|3|7|
       ---------
	  3|3

  */

  if (is_in_rectangle (x, y, (min_x - tollerance), (min_y - tollerance), (max_x + tollerance), (max_y + tollerance)))
    {
      double delta_x = (max_x-min_x)/4;
      double delta_y = (max_y-min_y)/4;

      if (is_in_rectangle (x, y, (min_x + delta_x), (min_y - tollerance), (max_x - delta_x), (min_y + delta_y)))
	{
	  zone = 1;
	  type = GDK_TOP_SIDE;
	}
      else if (is_in_rectangle (x, y, (min_x + delta_x), (max_y - delta_y), (max_x - delta_x), (max_y + tollerance)))
	{
	  zone = 3;
	  type = GDK_BOTTOM_SIDE;
	}
      else if (is_in_rectangle (x, y, (min_x - tollerance), (min_y + delta_y), (min_x + delta_x), (max_y - delta_y)))
	{
	  zone = 0;
	  type = GDK_LEFT_SIDE;
	}
      else if (is_in_rectangle (x, y, (max_x - delta_x), (min_y + delta_y), (max_x + tollerance), (max_y - delta_y)))
	{
	  zone = 2;
	  type = GDK_RIGHT_SIDE;
	}
      else if (is_in_rectangle (x, y, (min_x + delta_x), (min_y + delta_y), (max_x - delta_x), (max_y - delta_y)))
	{
	  zone = 5;
	}
      else if (is_in_rectangle (x, y, min_x, min_y, (min_x + delta_x), (min_y + delta_y)))
	{
	  zone = 4;
	  type = GDK_TOP_LEFT_CORNER;
	}
      else if (is_in_rectangle (x, y, (max_x - delta_x), min_y, max_x, (min_y + delta_y)))
	{
	  zone = 6;
	  type = GDK_TOP_RIGHT_CORNER;
	}
      else if (is_in_rectangle (x, y, (max_x - delta_x), (max_y - delta_y), max_x, max_y))
	{
	  zone = 7;
	  type = GDK_BOTTOM_RIGHT_CORNER;
	}
      else if (is_in_rectangle (x, y, min_x, (max_y - delta_y), (min_x + delta_x), max_y))
	{
	  zone = 8;
	  type = GDK_BOTTOM_LEFT_CORNER;
	}
    }
  if (cursor)
    {
      gdk_cursor_unref (cursor);
    }
  cursor = gdk_cursor_new (type);

}


/* Is the pointer above the close button? */
static gboolean
is_above_close_button (gint x, gint y)
{
  gint tollerance = 25;
  if ((x > (screen_width - tollerance)) &&
      (y < tollerance))
    {
      return TRUE;
    }
  return FALSE;
}


/* Destroy the program. */
G_MODULE_EXPORT gboolean 
destroy (GtkWidget *widget, gpointer data)
{
  quit ();
  return TRUE;
}


/* Exposure event. */
G_MODULE_EXPORT gboolean 
on_window_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
{
  gint is_fullscreen = gdk_window_get_state (widget->window) & GDK_WINDOW_STATE_FULLSCREEN;
  if (is_fullscreen <= 0)
    {
      return TRUE;
    }
  if (!cr)
    {
      screen_width = gdk_screen_width ();
      screen_height = gdk_screen_height ();
      cr = gdk_cairo_create (widget->window);

      /* Initialize the cairo context pen options. */
      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
      cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
      cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
    }

  color_window (cr, BACKGROUND_COLOR, screen_width);

  /* Initialize the shape for the input mask. */
  if (!shape)
    {
      shape = gdk_pixmap_new (NULL, screen_width, screen_height, 1);
    }
  if (!shape_cr)
    {
      shape_cr = gdk_cairo_create (shape);
    }

  return TRUE;
}


/* Called when a press event occurs. */
G_MODULE_EXPORT gboolean 
on_window_button_press_event (GtkWindow *window, GdkEventButton *event, gpointer data)
{
  if (is_above_close_button (event->x, event->y))
    {
      quit ();
    }
  if (first_press_event)
    {
      max_x = event->x + 150;
      max_y = event->y + 100;
      min_x = event->x - 150;
      min_y = event->y - 100;
      first_press_event = FALSE;
     }

#ifndef _WIN32
  gtk_widget_input_shape_combine_mask (GTK_WIDGET (window), NULL, 0, 0);
#endif
	
  return TRUE;
}


/* Called when a motion event occurs. */
G_MODULE_EXPORT gboolean 
on_window_motion_event (GtkWindow *window, GdkEventButton *event, gpointer data)
{
  GdkModifierType state = (GdkModifierType) event->state; 
  /* Only if button1 is not pushed. */
  if (!(state & GDK_BUTTON1_MASK))
    {
      if (is_above_close_button (event->x, event->y))
        {
          gdk_window_set_cursor (GTK_WIDGET (window)->window, NULL);
        }
      else
        {
          discriminate_zone (event->x,event->y);  
          gdk_window_set_cursor (GTK_WIDGET (window)->window, cursor);
        }
    }
  return TRUE;
}


/* Called when a release event occurs. */
G_MODULE_EXPORT gboolean 
on_window_button_release_event (GtkWindow *window, GdkEventButton *event, gpointer data)
{
  /* Only button1 allowed. */
  if (event->button != 1)
    {
      return FALSE;
    }

  gint spotlighter_minimum_size = 60;
  gdk_window_set_cursor (GTK_WIDGET (window)->window, NULL);
  
  if (cr)
    {
      color_window (cr, BACKGROUND_COLOR, screen_width);
    }

  if (zone == 5)
    {
      gdouble centerdelta_x = event->x - (min_x + (max_x - min_x)/2);
      gdouble centerdelta_y = event->y - (min_y + (max_y - min_y)/2);
      min_x = min_x + centerdelta_x;
      min_y = min_y + centerdelta_y;
      max_x = max_x + centerdelta_x;
      max_y = max_y + centerdelta_y;
    }
  else if (zone == 1)
    {
      if (fabs (min_y - event->y) > spotlighter_minimum_size)
	{
	  min_y = event->y;
	}
    }
  else if (zone == 3)
    {
      if (fabs (min_y - event->y) > spotlighter_minimum_size)
	{
	  max_y = event->y;
	}
    }
  else if (zone == 0)
    {
      if (fabs (min_x - event->x) > spotlighter_minimum_size)
	{
	  min_x = event->x;
	}
    }
  else if (zone == 2)
    {
      if (fabs (min_x - event->x) > spotlighter_minimum_size)
	{
	  max_x = event->x;
	}
    }
  else if (zone == 4)
    {
      if ((fabs (max_y - event->y) > spotlighter_minimum_size) &&
          (fabs (max_x - event->x) > spotlighter_minimum_size))
	{
	  min_x = event->x;
	  min_y = event->y;
	}
    }
  else if (zone == 6)
    {
      if ((fabs (max_y - event->y) > spotlighter_minimum_size) &&
          (fabs (min_x - event->x) > spotlighter_minimum_size))
	{
	  min_y = event->y;
	  max_x = event->x;
	}
    }
  else if (zone == 7)
    {
      if ((fabs (min_y-event->y) > spotlighter_minimum_size) &&
          (fabs (min_x-event->x) > spotlighter_minimum_size))
	{
	  max_x = event->x;
	  max_y = event->y;
	}
    }
  else if (zone == 8)
    {
      if ((fabs (min_y-event->y) > spotlighter_minimum_size) &&
          (fabs (max_x-event->x) > spotlighter_minimum_size))
	{
	  max_y = event->y;
	  min_x = event->x;
	}
    }

  draw (cr, min_x, min_y, (max_x - min_x), (max_y - min_y), SPOTLIGHTER_COLOR);
  reset_shape ();
  draw (shape_cr, min_x, min_y, (max_x - min_x), (max_y - min_y), SPOTLIGHTER_COLOR);

  /* This allow the mouse focus below the transparent hole. */
#ifndef _WIN32
  gtk_widget_input_shape_combine_mask (GTK_WIDGET (window), shape, 0, 0);
#else
  /* Release the mouse.	*/
  gtk_widget_shape_combine_mask (GTK_WIDGET (window), shape, 0, 0);
#endif

  return TRUE;
}


/* Called when a destroy event occurs. */
G_MODULE_EXPORT gboolean 
on_window_destroy_event (GtkWindow *self, GdkEvent *event, gpointer user_data)
{
  quit ();
  return TRUE;
}


